package solver

import (
	"fmt"
	"gosudoku/set"
	"log"
	"slices"
	"strings"
)

func (g *Grid) sdcHouses(alFunc func([]*Cell, House, House) bool) (used bool) {
	for boxNum := 0; boxNum < 9; boxNum++ {
		box := g.boxes[boxNum]
		rowStart := boxNum / 3 * 3
		colStart := boxNum % 3 * 3
		for rowNum := rowStart; rowNum < rowStart+3; rowNum++ {
			row := g.rows[rowNum]
			cells := row.cells[colStart : colStart+3]

			if alFunc(cells, row, box) {
				return true
			}
		}
		for colNum := colStart; colNum < colStart+3; colNum++ {
			col := g.cols[colNum]
			cells := col.cells[rowStart : rowStart+3]

			if alFunc(cells, col, box) {
				return true
			}
		}
	}
	return
}

func (g *Grid) SuedeCoq1() (used bool) {
	return g.sdcHouses(g.SDC1)
}

func (g *Grid) SuedeCoq2() (used bool) {
	return g.sdcHouses(g.SDC2)
}

func (g *Grid) SDC1(shares []*Cell,
	house1, house2 House) (used bool) {

	houseCells1 := sdcCells(shares, house1)
	houseCells2 := sdcCells(shares, house2)

	for _, pivotCells := range combine(getUnsolvedCells(shares), 2) {
		pivotCands := pivotCells[0].cands.Union(pivotCells[1].cands)
		if pivotCands.Count() != 4 {
			continue
		}
		for _, cells1 := range sdcCellCombine(houseCells1) {
			ok1, cands1 := verifySDCFirst(pivotCands, cells1)
			if !ok1 {
				continue
			}
			for _, cells2 := range sdcCellCombine(houseCells2) {
				ok2, cands2 := verifySDCSecond(pivotCands, cands1, cells2)
				if !ok2 {
					continue
				}
				if removeSDC1(pivotCells, pivotCands, cells1, cands1, house1, cells2, cands2, house2) {
					logSDC1(pivotCells, pivotCands, cells1, cands1, cells2, cands2)
					used = true
					return
				}
			}
		}
	}
	return
}

func (g *Grid) SDC2(shares []*Cell,
	house1, house2 House) (used bool) {

	houseCells1 := sdcCells(shares, house1)
	houseCells2 := sdcCells(shares, house2)

	for _, cell := range shares {
		if cell.solved() {
			return
		}
	}

	pivotCells := shares

	pivotCands := pivotCells[0].cands.Union(
		pivotCells[1].cands).Union(pivotCells[2].cands)
	if pivotCands.Count() != 5 {
		return
	}

	for _, cells1 := range sdcCellCombine(houseCells1) {
		ok1, cands1 := verifySDCFirst(pivotCands, cells1)
		if !ok1 {
			continue
		}
		for _, cells2 := range sdcCellCombine(houseCells2) {
			ok2, cands2 := verifySDCSecond(pivotCands, cands1, cells2)
			if !ok2 {
				continue
			}
			if removeSDC1(pivotCells, pivotCands, cells1, cands1, house1, cells2, cands2, house2) {
				logSDC1(pivotCells, pivotCands, cells1, cands1, cells2, cands2)
				used = true
				return
			}
		}
	}

	return
}

func sdcCells(shares []*Cell, house House) []*Cell {
	cells := []*Cell{}
	for _, cell := range house.cells {
		if !cell.solved() && !slices.Contains(shares, cell) {
			cells = append(cells, cell)
		}
	}
	return cells
}

func sdcCellCombine(cells []*Cell) [][]*Cell {
	cc := [][]*Cell{}
	for i := 1; i < len(cells); i++ {
		cc = append(cc, combine(cells, i)...)
	}
	return cc
}

func verifySDCFirst(pivotCands set.Set, cells []*Cell) (ok bool, cands set.Set) {
	cands = cells[0].cands
	for _, cell := range cells[1:] {
		cands = cands.Union(cell.cands)
	}
	partCands := cands.Intersect(pivotCands)
	n := len(cells)
	diff := cands.Diff(partCands)
	if partCands.Count() != 2 || diff.Count() != n-1 {
		return
	}
	if diff.Intersect(pivotCands).Count() != 0 {
		return
	}
	ok = true
	return
}

func verifySDCSecond(pivotCands, cands1 set.Set, cells []*Cell) (ok bool, cands set.Set) {
	cands = cells[0].cands
	for _, cell := range cells[1:] {
		cands = cands.Union(cell.cands)
	}
	partCands := cands.Intersect(pivotCands)
	n := len(cells)
	diff := cands.Diff(partCands)
	if partCands.Count() != 2 || diff.Count() != n-1 {
		return
	}
	if diff.Intersect(pivotCands).Count() != 0 {
		return
	}
	if pivotCands.Intersect(cands1).Intersect(partCands).Count() != 0 {
		return
	}
	ok = true
	return
}

func removeSDC1(pivotCells []*Cell, pivotCands set.Set,
	cells1 []*Cell, cands1 set.Set, house1 House,
	cells2 []*Cell, cands2 set.Set, house2 House) (used bool) {
	fifth := pivotCands.Diff(cands1).Diff(cands2)

	for _, cell := range house1.cells {
		if cell.solved() || slices.Contains(pivotCells, cell) ||
			slices.Contains(cells1, cell) {
			continue
		}
		if cell.removeCands(cands1.Union(fifth).GetList()) {
			used = true
		}
	}
	for _, cell := range house2.cells {
		if cell.solved() || slices.Contains(pivotCells, cell) ||
			slices.Contains(cells2, cell) {
			continue
		}
		if cell.removeCands(cands2.Union(fifth).GetList()) {
			used = true
		}
	}
	return
}

func logSDC1(pivotCells []*Cell, pivotCands set.Set,
	cells1 []*Cell, cands1 set.Set, cells2 []*Cell, cands2 set.Set) {
	var b strings.Builder
	logCells(&b, pivotCells)
	fmt.Fprintf(&b, "%v ", pivotCands.ValueString())
	logCells(&b, cells1)
	fmt.Fprintf(&b, "%v ", cands1.ValueString())
	logCells(&b, cells2)
	fmt.Fprintf(&b, "%v ", cands2.ValueString())
	fmt.Fprint(&b, "Sue de Coq")
	log.Print(b.String())

}
